<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <style>
        /* resize */
        #gameArea {
            position: absolute;
            left: 50%;
            top: 50%;
            background-color: azure;
        }

        #gameCanvas {
            width: 100%;
            height: 100%;
            /* background-color: burlywood; */
        }

        #statsPanel {
            position: absolute;
            width: 100%;
            height: 8%;
            bottom: 0;
            opacity: 0.8;
            /* opacity: 0; */
            background-color: darkcyan;
        }
    </style>
</head>

<body>

    <div id="gameArea">
        <canvas id="gameCanvas"></canvas>
        <div id="statsPanel"></div>
    </div>

    <script>
        // grid system
        class GridSystem {
            // rows: 300,
            // columns: 400,
            static rows = 900
            static columns = 1600
            static ratio = 16 / 9

            static context = null

            static width = 0
            static height = 0
            static cellSize = 0

            static SetContext(context) {
                GridSystem.context = context
            }

            static SetParams(width, height) {
                GridSystem.width = width
                GridSystem.height = height
                GridSystem.cellSize = width / GridSystem.columns
            }

            static DrawGrids() {
                // this.context.fillStyle = "pink"
                this.context.beginPath()
                for (var i = 0; i < this.columns; i++) {
                    this.context.moveTo(i * this.cellSize, 0);
                    this.context.lineTo(i * this.cellSize, this.height);
                }
                for (var i = 0; i < this.rows; i++) {
                    this.context.moveTo(0, i * this.cellSize);
                    this.context.lineTo(this.width, i * this.cellSize);
                }
                this.context.stroke()
            }
        }

        var canvas = document.getElementById('gameCanvas');
        var context = canvas.getContext('2d');
        var reqId;
        var oldTimeStamp
        let gameObjects;
        GridSystem.SetContext(context)

        // resize
        function resizeGame() {
            var gameArea = document.getElementById('gameArea');

            // var widthToHeight = 4 / 3;
            // var widthToHeight = 16 / 9;
            var widthToHeight = GridSystem.ratio;

            var newWidth = window.innerWidth;
            var newHeight = window.innerHeight;
            var newWidthToHeight = newWidth / newHeight;

            if (newWidthToHeight > widthToHeight) {
                newWidth = newHeight * widthToHeight;
                gameArea.style.height = newHeight + 'px';
                gameArea.style.width = newWidth + 'px';
            } else {
                newHeight = newWidth / widthToHeight;
                gameArea.style.width = newWidth + 'px';
                gameArea.style.height = newHeight + 'px';
            }

            gameArea.style.marginTop = (-newHeight / 2) + 'px';
            gameArea.style.marginLeft = (-newWidth / 2) + 'px';

            var gameCanvas = document.getElementById('gameCanvas');
            gameCanvas.width = newWidth;
            gameCanvas.height = newHeight;

            // console.log(newWidth)
            // console.log(newHeight)

            // restart the game
            RedrawGame()
        }

        window.addEventListener('resize', resizeGame, false);
        window.addEventListener('orientationchange', resizeGame, false);
        window.addEventListener('load', resizeGame, false);


        class GameObject {
            constructor(context, x, y, vx, vy, name) {
                this.context = context;
                this.x = x;
                this.y = y;
                this.vx = vx;
                this.vy = vy;

                this.name = name

                this.isColliding = false;
            }
        }

        class Square extends GameObject {
            // Set default width and height
            // static width = 50;
            // static height = 50;

            constructor(context, x, y, vx, vy, name) {
                super(context, x, y, vx, vy, name);

                this.width = 50;
                this.height = 50;
            }

            draw() {
                // console.log(this.x, this.y)
                // Draw a simple square
                this.context.fillStyle = this.isColliding ? '#ff8080' : '#0099b0';
                this.context.fillRect(this.x, this.y, this.width, this.height);

                this.context.fillStyle = 'white';
                this.context.font = '18px serif';
                this.context.fillText(this.name, this.x, this.y + this.height / 2);
            }

            update(secondsPassed) {
                // Move with set velocity
                this.x += this.vx * secondsPassed;
                this.y += this.vy * secondsPassed;
            }
        }

        /**
         * Cell的坐标应该是通用的中立的，不是实际的像素
         *      cell.x in [0, columns)
         *      cell.y in [0, rows)
         * 玩家之间同步位置的时候用的就是这个
         * 所有玩家的不同屏幕都是抽象的 columns * rows 的大小
         * 所以 x, y, vx, vy 都是相对于这个抽象的画布来说的
         *
         * 当绘制到canvas上面的时候，需要转换成像素
         *      cell.x * cellSize
         *      cell.y * cellSize
         *
         * 注意坐标转化后的像素不能超出canvas的边界
         *
         */
        class Cell extends GameObject {
            // SyncVirtualLocation(virtual_x, virtual_y) {
            //     this.virtual_x = virtual_x;
            //     this.virtual_y = virtual_y;
            // }

            // Set default width and height
            // static width = 50;
            // static height = 50;

            constructor(context, x, y, vx, vy, name) {
                super(context, x, y, vx, vy, name);

                this.speedAccelerator = 10
            }

            draw() {
                // radius is 10 times of cell size
                this.radius = 30
                // this.radius = this.radius * GridSystem.cellSize
                this.width = this.radius * 2
                this.height = this.radius * 2


                // console.log(this.x, this.y)
                // console.log(this)
                // Draw a simple square
                // console.log(GridSystem.cellWidth, GridSystem.cellHeight)
                this.context.fillStyle = this.isColliding ? '#ff8080' : '#0099b0';
                // this.context.fillRect(this.x * GridSystem.cellWidth, this.y * GridSystem.cellHeight,
                //     GridSystem.cellWidth, GridSystem.cellHeight);

                this.context.beginPath();
                this.context.arc(
                    // center
                    this.x * GridSystem.cellSize, this.y * GridSystem.cellSize,
                    // up left corner
                    // this.x * GridSystem.cellSize + this.radius, this.y * GridSystem.cellSize + this.radius,
                    // radius
                    this.radius * GridSystem.cellSize,
                    // angle from to
                    0, 2 * Math.PI);
                this.context.fill();


                this.context.strokeStyle = 'green';
                this.context.strokeRect(this.x * GridSystem.cellSize - this.radius * GridSystem.cellSize, this.y * GridSystem.cellSize - this.radius * GridSystem.cellSize,
                    this.width * GridSystem.cellSize, this.height * GridSystem.cellSize);

                this.context.fillStyle = 'black';
                this.context.font = '18px serif';
                this.context.fillText(this.name,
                    this.x * GridSystem.cellSize - this.radius * GridSystem.cellSize,
                    this.y * GridSystem.cellSize);
                // this.context.fillText(this.degrees, this.x * GridSystem.cellSize, this.y * GridSystem.cellSize + this.height);
                // this.context.fillText(Math.cos(this.radians), this.x * GridSystem.cellSize, this.y * GridSystem.cellSize + this.height+20);
                // this.context.fillText(Math.sin(this.radians), this.x * GridSystem.cellSize, this.y * GridSystem.cellSize + this.height+40);


                this.context.moveTo(this.x * GridSystem.cellSize,
                    this.y * GridSystem.cellSize);
                this.context.lineTo(this.x * GridSystem.cellSize
                    + 2 * this.radius * GridSystem.cellSize * Math.cos(this.radians),
                    this.y * GridSystem.cellSize
                    + 2 * this.radius * GridSystem.cellSize * Math.sin(this.radians));
                this.context.stroke();

            }

            update(secondsPassed) {
                // Move with set velocity
                // smoothly move
                // behave same (move speed relative to the canvas size) in all screen size
                // TODO: 更新的应该是中立的坐标，而不是像素
                this.x += this.vx * secondsPassed * this.speedAccelerator
                this.y += this.vy * secondsPassed * this.speedAccelerator


                // Calculate the angle (vy before vx)
                let radians = Math.atan2(this.vy, this.vx);
                // Convert to degrees
                let degrees = 180 * radians / Math.PI;
                this.radians = radians
                this.degrees = degrees

                // this.x += this.vx * secondsPassed * GridSystem.cellWidth;
                // this.y += this.vy * secondsPassed * GridSystem.cellHeight;
                // this.x += this.vx * 1 * GridSystem.cellWidth;
                // this.y += this.vy * 1 * GridSystem.cellHeight;
            }
        }


        class Player extends Cell {
            constructor(context, x, y, vx, vy, name) {
                super(context, x, y, vx, vy, name);

                this.speedAccelerator = 30
            }
        }

        function detectCollisions() {
            let obj1;
            let obj2;

            // Reset collision state of all objects
            for (let i = 0; i < gameObjects.length; i++) {
                gameObjects[i].isColliding = false;
            }

            // Start checking for collisions
            for (let i = 0; i < gameObjects.length; i++) {
                obj1 = gameObjects[i];
                for (let j = i + 1; j < gameObjects.length; j++) {
                    obj2 = gameObjects[j];

                    // Compare object1 with object2
                    // if (rectIntersect(obj1.x, obj1.y, obj1.width, obj1.height, obj2.x, obj2.y, obj2.width, obj2.height)) {
                    if (circleIntersect(obj1.x * GridSystem.cellSize, obj1.y * GridSystem.cellSize, obj1.radius * GridSystem.cellSize,
                        obj2.x * GridSystem.cellSize, obj2.y * GridSystem.cellSize, obj2.radius * GridSystem.cellSize)) {
                        // console.log(i, j)
                        // console.log(obj1, obj2)
                        obj1.isColliding = true;
                        obj2.isColliding = true;
                    }
                }
            }
        }

        function rectIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
            // console.log(x1, y1, w1, h1, x2, y2, w2, h2)
            // console.log(x2 > w1 + x1)
            // console.log(x1 > w2 + x2)
            // console.log(y2 > h1 + y1)
            // console.log(y1 > h2 + y2)
            // Check x and y for overlap
            if (x2 > w1 + x1 || x1 > w2 + x2 || y2 > h1 + y1 || y1 > h2 + y2) {
                return false;
            }
            return true;
        }

        function circleIntersect(x1, y1, r1, x2, y2, r2) {
            // Calculate the distance between the two circles
            let squareDistance = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);

            // When the distance is smaller or equal to the sum
            // of the two radius, the circles touch or overlap
            return squareDistance <= ((r1 + r2) * (r1 + r2))
        }

        function detectEdgeCollisions() {
            // Define the edges of the canvas
            const canvasWidth = 1600;
            const canvasHeight = 900;

            // Set a restitution, a lower value will lose more energy when colliding
            // const restitution = 0.90;
            const restitution = 1;
            let obj;
            for (let i = 0; i < gameObjects.length; i++) {
                obj = gameObjects[i];

                // Check for left and right
                if (obj.x < obj.radius) {
                    obj.vx = Math.abs(obj.vx) * restitution;
                    obj.x = obj.radius;
                } else if (obj.x > canvasWidth - obj.radius) {
                    obj.vx = -Math.abs(obj.vx) * restitution;
                    obj.x = canvasWidth - obj.radius;
                }

                // Check for bottom and top
                if (obj.y < obj.radius) {
                    obj.vy = Math.abs(obj.vy) * restitution;
                    obj.y = obj.radius;
                } else if (obj.y > canvasHeight - obj.radius) {
                    obj.vy = -Math.abs(obj.vy) * restitution;
                    obj.y = canvasHeight - obj.radius;
                }
            }
        }

        function createWorld() {
            gameObjects = [
                // new Square(context, 250, 50, 0, 50, 's1'),
                // new Square(context, 250, 300, 0, -50, 's2'),
                // new Square(context, 150, 0, 50, 50, 's3'),
                // new Square(context, 250, 150, 50, 50, 's4'),
                // new Square(context, 350, 75, -50, 50, 's5'),
                // new Square(context, 300, 300, 50, -50, 's6'),
                new Cell(context, 30, 80, 30, 10, 'c1'),
                new Cell(context, 60, 90, 20, 10, 'c2'),
                new Cell(context, 20, 60, 50, 20, 'c3'),
                new Cell(context, 50, 100, 10, 10, 'c4'),
                new Cell(context, 500, 300, -10, -20, 'c4'),
                // player
                new Player(context, 500, 300, -10, -20, 'player'),
            ];
        }

        function gameLoop(timeStamp) {
            // Notice: call timeStamp is seconds
            // console.log('what',timeStamp)
            var timeStamp = (new Date()).getTime()
            // console.log('this',timeStamp)

            // if (timeStamp - oldTimeStamp < 100) {
            //     reqId = window.requestAnimationFrame(gameLoop);
            //     return;
            // }

            var secondsPassed = (timeStamp - oldTimeStamp) / 1000;
            oldTimeStamp = timeStamp;
            // console.log(secondsPassed)
            // console.log(gameObjects.length)

            // Loop over all game objects
            for (let i = 0; i < gameObjects.length; i++) {
                gameObjects[i].update(secondsPassed);
                // gameObjects[i].update();
            }

            detectCollisions()
            detectEdgeCollisions()

            clearCanvas();

            // Do the same to draw
            for (let i = 0; i < gameObjects.length; i++) {
                gameObjects[i].draw();
            }

            // draw
            // draw()
            // GridSystem.DrawGrids()

            reqId = window.requestAnimationFrame(gameLoop);
        }

        function clearCanvas() {
            context.clearRect(0, 0, canvas.width, canvas.height);
        }


        function draw() {
            var ctx = context

            // Filled triangle
            ctx.beginPath();
            ctx.moveTo(25, 25);
            ctx.lineTo(105, 25);
            ctx.lineTo(25, 105);
            ctx.fill();

            // Stroked triangle
            ctx.beginPath();
            ctx.moveTo(125, 125);
            ctx.lineTo(125, 45);
            ctx.lineTo(45, 125);
            ctx.closePath();
            ctx.stroke();
        }


        createWorld()

        function RedrawGame() {
            endGame()

            // grids
            // grids = new GridSystem(context, canvas.width, canvas.height, config.rows, config.columns)
            GridSystem.SetParams(canvas.width, canvas.height)
            // console.log(GridSystem.width)
            // console.log(GridSystem.height)
            // console.log(GridSystem.cellSize)
            // console.log(GridSystem.columns)
            // console.log(GridSystem.rows)

            oldTimeStamp = (new Date()).getTime()
            // reqId = window.requestAnimationFrame(gameLoop)

        }

        function endGame() {
            if (reqId) {
                window.cancelAnimationFrame(reqId)
            }
        }


        var ctx = context
        /* // Set up mouse events for drawing
        var drawing = false;
        var mousePos = { x: 0, y: 0 };
        var lastPos = mousePos;
        canvas.addEventListener("mousedown", function (e) {
            drawing = true;
            lastPos = getMousePos(canvas, e);
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            drawing = false;
        }, false);
        canvas.addEventListener("mousemove", function (e) {
            mousePos = getMousePos(canvas, e);
        }, false);

        // Get the position of the mouse relative to the canvas
        function getMousePos(canvasDom, mouseEvent) {
            var rect = canvasDom.getBoundingClientRect();
            return {
                x: mouseEvent.clientX - rect.left,
                y: mouseEvent.clientY - rect.top
            };
        }
        // Get a regular interval for drawing to the screen
        window.requestAnimFrame = (function (callback) {
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimaitonFrame ||
                function (callback) {
                    window.setTimeout(callback, 1000 / 60);
                };
        })();
        // Draw to the canvas
        function renderCanvas() {
            if (drawing) {
                ctx.moveTo(lastPos.x, lastPos.y);
                ctx.lineTo(mousePos.x, mousePos.y);
                ctx.stroke();
                lastPos = mousePos;
            }
        }

        // Allow for animation
        (function drawLoop() {
            requestAnimFrame(drawLoop);
            renderCanvas();
        })(); */


        // setup lines styles .. 
        ctx.strokeStyle = "#DDD";
        ctx.lineWidth = 2;

        // some variables we'll need .. 
        var drawing = false;
        var mousePos = { x: 0, y: 0 };
        var lastPos = mousePos;
        var isMobile = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;

        // mouse/touch events ..
        canvas.addEventListener((isMobile ? 'touchstart' : 'mousedown'), function (e) {
            drawing = true;
            lastPos = getMousePos(canvas, e);
            mousePos = lastPos;
        });
        canvas.addEventListener((isMobile ? 'touchmove' : 'mousemove'), function (e) {
            mousePos = getMousePos(canvas, e);
        });
        canvas.addEventListener((isMobile ? 'touchend' : 'mouseup'), function (e) {
            drawing = false;
        });

        // helper functions .. 
        function getMousePos(canvasDom, touchOrMouseEvent) {
            var rect = canvasDom.getBoundingClientRect();
            return {
                x: (isMobile ? touchOrMouseEvent.touches[0].clientX : touchOrMouseEvent.clientX) - rect.left,
                y: (isMobile ? touchOrMouseEvent.touches[0].clientY : touchOrMouseEvent.clientY) - rect.top
            };
        };

        // drawing .. 
        window.requestAnimFrame = (function (callback) {
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function (callback) {
                    window.setTimeout(callback, 1000 / 60);
                };
        })();

        function renderCanvas() {
            if (drawing) {
                ctx.moveTo(lastPos.x, lastPos.y);
                ctx.lineTo(mousePos.x, mousePos.y);
                ctx.stroke();
                lastPos = mousePos;
            }
        };

        (function drawLoop() {
            requestAnimFrame(drawLoop);
            renderCanvas();
        })();

    // startGame()

    </script>
</body>

</html>